package com.baranbaygan.downloadmanager
{
	import com.baranbaygan.downloadmanager.downloadtracker.*;
	import com.baranbaygan.downloadmanager.events.DownloadPackageEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	import mx.collections.ArrayCollection;
	
	public class FileDownloadManager extends EventDispatcher
	{
		public static const SpeedTimerInterval:Number=500;

		/**
		 * The sum of download speeds of all the files being downloaded.
		 */ 
		private var downloadSpeed:Number;
				
		/**
		 * This timer is used to calculate the speed of the downloads.
		 */
		private var speedTimer:Timer;
		
		/**
		 * Not completed files will be written into this folder as raw data and *.temp format.
		 */ 
		private var tempDownloadFolder:String;
		/**
		 * Files finished downloading will be written here.
		 */ 
		private var downloadFolder:String;

		public var downloadTracker:DownloadTracker;
		
		private var downloadPackages:ArrayCollection = new ArrayCollection();
		
		private var downloadFiles:ArrayCollection=new ArrayCollection();
		
		/* -------------------------------------------------------
		*	Getter, Setters
		*
		* ------------------------------------------------------- */

		public function get DownloadSpeed():Number
		{
			return this.downloadSpeed;
		}
		
		public function get DownloadList():XMLList
		{
			return this.downloadTracker.trackXml.DownloadPackage;
		}
			
		public function set TempDownloadFolder(value:String):void
		{	
			this.tempDownloadFolder = value;
			this.downloadTracker = new DownloadTracker( this.tempDownloadFolder + "/tracker.xml" );
		}
		public function get TempDownloadFolder():String{ return this.tempDownloadFolder; }
		public function set DownloadFolder(value:String):void{ this.downloadFolder = value;	}
		public function get DownloadFolder():String{ return this.downloadFolder; }

		public function FileDownloadManager()
		{
			super(null);

			
			this.downloadFiles = new ArrayCollection(new Array());
		}
		
		/* -------------------------------------------------------
		*	Public functions
		* ------------------------------------------------------- */
		
		public function PackageAlreadyDownloading(packageId:String):Boolean
		{
			return this.downloadTracker.PackageAlreadyDownloading(packageId);
		}
		
		/**
		 * This function initializes the variables from the file and starts the previous downloads.
		 */ 
		public function StartDownloadManager():void
		{
			this.speedTimer = new Timer(FileDownloadManager.SpeedTimerInterval);
			this.speedTimer.addEventListener(TimerEvent.TIMER, timerTick);
			this.speedTimer.start();
			
			var packages:XML = this.downloadTracker.DownloadPackageList;
			
			// Read the packages from the xml which are not completed
			for each(var packageXml:XML in packages.DownloadPackage.(Status != DownloadStatus.COMPLETED))
			{
				// Create a download package with the properties of the xml node in the tracker file.				
				var downloadPackage:DownloadPackage = new DownloadPackage(packageXml, this);
				
				configureDownloadPackageListeners(downloadPackage);
				
				// Add the package to the list
				this.downloadPackages.addItem(downloadPackage);
			}
		}
		
		public function ShutdownDownloadManager():void
		{
			this.downloadTracker.SaveTrackerFile();
		}

		public function AddDownloadPackage(fileInfoList:Array, packageName:String, packageId:String, externalData:Object=null):void
		{
			if(this.downloadTracker.PackageAlreadyAdded(packageId))
			{
				return;
			}

			var packageXml:XML = <DownloadPackage />;
			packageXml.PackageName = packageName;
			packageXml.PackageID = packageId;
			packageXml.Status = DownloadStatus.QUEUED;
			packageXml.Speed = "0";
			packageXml.BytesCompleted = "0";
			packageXml.BytesTotal = "0";

			for each(var fileInfo:FileInfo in fileInfoList)
			{
				var downloadFileProperties:XML = <file />
				downloadFileProperties.Url = fileInfo.Url;
				downloadFileProperties.FileName = fileInfo.FileName;
				downloadFileProperties.FileId = fileInfo.FileId;
				downloadFileProperties.CompletedBytes = "0";
				downloadFileProperties.TotalBytes = "0";
				downloadFileProperties.Status = DownloadStatus.QUEUED;
				downloadFileProperties.Speed = "0";
				
				downloadFileProperties.appendChild(fileInfo.ExternalData);
				
				packageXml.appendChild(downloadFileProperties);		
			}

			// External data. This is used for general purposes to track the download.
			var externalDataXml:XML = <ExternalData/>;
			if(externalData==null)
			{
				externalData = new Object();
			}
			for (var key:String in externalData)
			{
				var keyNode:XML = <data/>;
				keyNode.key=key;
				keyNode.data=externalData[key];
				externalDataXml.appendChild(keyNode);
			}
			packageXml.appendChild(externalDataXml);
			
			var dPackage:DownloadPackage = new DownloadPackage(packageXml, this);
			
			this.downloadTracker.AddPackage(dPackage);
			
			configureDownloadPackageListeners(dPackage);
			
			this.downloadPackages.addItem(dPackage);
		}
		
		public function PauseDownload(packageId:String):void
		{
			var p:DownloadPackage = GetDownloadPackage(packageId);
			
			if(p.Status != DownloadStatus.COMPLETED)
			{
				p.Status = DownloadStatus.PAUSED;
			}
		}
		
		public function StartDownload(packageId:String):void
		{
			var p:DownloadPackage = GetDownloadPackage(packageId);
			
			if(p.Status != DownloadStatus.COMPLETED)
			{
				p.Status = DownloadStatus.DOWNLOADING;
			}
		}
		
		public function CancelDownload(packageId:String):void
		{
			var p:DownloadPackage = GetDownloadPackage(packageId);
			
			// p is null if the package is completed. I don't add the completed packages to the array from the xml.
			if(p != null)
			{
				p.CancelDownload();
			
				
				// Clear the listeners for this package
				clearDownloadPackageListeners(p);
	
				// Remove it from the array
				RemoveDownloadPackage(packageId);
				
				// Dispatch an event for the application using this library
				var e:DownloadPackageEvent = new DownloadPackageEvent(Event.CANCEL);
				e.TargetDownloadPackage=p;
				this.dispatchEvent(e);
	
				this.downloadTracker.RemovePackage(p);
			}
			else
			{
				// In this case we only have to remove the package from the tracker. So it disapears from the list.
				this.downloadTracker.RemovePackagebyID(packageId);
			}
		}
		
		/*
		public function PauseDownload(url:String):void
		{
			// Check if this url is downloading
			if(this.downloadTracker.UrlAlreadyDownloading(url))
			{
				// Find the package this file is in
				var pId:String = XML(XML(this.downloadTracker.DownloadPackageList.downloadPackage.file.(Url == url)[0]).parent()).PackageID; 
				var fileId:String = XML(this.downloadTracker.DownloadPackageList.downloadPackage.file.(Url == url)[0]).FileID;
				
				for each(var p:DownloadPackage in this.downloadPackages)
				{
					if(p.PackageID == pId)
					{
						p.PauseDownload(fileId);
						break;
					}
				}
			}
		}
		
		public function StartDownload(url:String):void
		{		
			// Check if this url is downloading
			if(this.downloadTracker.UrlAlreadyDownloading(url))
			{
				// Find the package this file is in
				var pId:String = XML(XML(this.downloadTracker.DownloadPackageList.downloadPackage.file.(Url == url)[0]).parent()).PackageID; 
				var fileId:String = XML(this.downloadTracker.DownloadPackageList.downloadPackage.file.(Url == url)[0]).FileID;
				
				for each(var p:DownloadPackage in this.downloadPackages)
				{
					if(p.PackageID == pId)
					{
						p.StartDownload(fileId);
						break;
					}
				}
			}
		}
		*/
		
		/* -------------------------------------------------------
		*	Private functions
		* ------------------------------------------------------- */
		
		private function GetDownloadPackage(packageId:String):DownloadPackage
		{
			for each(var p:DownloadPackage in this.downloadPackages)
			{
				if(p.PackageID == packageId)
					return p;
			}
			
			return null;
		}
		
		private function RemoveDownloadPackage(packageId:String):Boolean
		{
			for (var i:Number=0; i<this.downloadPackages.length; i++)
			{
				if(this.downloadPackages[i].PackageID == packageId)
				{
					this.downloadPackages.removeItemAt(i);
					return true;
				}
			}
			return false;
		}
		
		private function GetDownloadSpeed():Number
		{
			var speed:Number=0;
			for each(var dFile:DownloadFile in this.downloadFiles)
			{
				if(dFile.Status==DownloadStatus.DOWNLOADING)
					speed = dFile.Speed;
			}
			return speed;
		}
		
		private function configureDownloadPackageListeners(obj:EventDispatcher):void
		{
			obj.addEventListener(Event.COMPLETE, completeHandler);
			
			this.speedTimer.addEventListener(TimerEvent.TIMER, DownloadPackage(obj).Timer_Tick);			

			this.downloadTracker.configureDownloadPackageListeners(obj);
		}
		
		private function clearDownloadPackageListeners(obj:EventDispatcher):void
		{
			obj.removeEventListener(Event.COMPLETE, completeHandler);
			this.speedTimer.removeEventListener(TimerEvent.TIMER, DownloadPackage(obj).Timer_Tick);
		}

		private function completeHandler(event:DownloadPackageEvent):void
		{
			var e:DownloadPackageEvent = new DownloadPackageEvent(Event.COMPLETE);
			e.TargetDownloadPackage = event.TargetDownloadPackage;
			e.EventTargetDownloadFile = event.EventTargetDownloadFile;
			this.dispatchEvent(e);
		}
		private function timerTick(event:TimerEvent):void
		{
			this.downloadSpeed=GetDownloadSpeed();
			
			// check if there is a package being downloaded. If not start a package
			var anyPackageDownloading:Boolean=false;
			for each(var dp:DownloadPackage in this.downloadPackages)
			{
				if(dp.Status==DownloadStatus.DOWNLOADING)
				{
					anyPackageDownloading = true;
					break;
				}
			}
			
			// Lets start downloading first available package
			if(anyPackageDownloading==false)
				StartAQueuedDownloadPackage();
		}
		
		private function StartAQueuedDownloadPackage():void
		{
			for each(var dP:DownloadPackage in this.downloadPackages)
			{
				if(dP.Status == DownloadStatus.QUEUED)
				{
					// By changing the state of the package we make sure that the Timer_Tick() function gets executed in DownloadPackage class.
					// Timer_Tick function returns if the status of the package is Queued.
					// Timer_Tick() function starts the first available queued download file in it.
					dP.Status = DownloadStatus.DOWNLOADING;
					break;
				}
			}
		}
	}
}













